## Author : Wenpeng WEI ( mailto: wenpeng.wei[at]gmail.com )
## Some code came from DongYuwei ( mailto:newdongyuwei[at]gmail.com ). Hats off to DongYuwei
## 2009/11/19

require 'rubygems'
require 'SIPCPackage'
require 'eventmachine'
require 'uri'  
require 'net/http'  
require 'net/https'  
require "socket"  
require 'rexml/document'  
require 'digest/md5'    
require 'digest/sha1'   
require "iconv"
require 'Contact'
require 'logger'

class Fetion
  
  #attr_accessor :phone_num, :password, :domain, :login_xml, :server_ip, :server_port, :nonce, :cnonce, :fetion_num, :fetion_conn
  attr_reader :response_handlers, :response_handler_args, :logger, :off_ui_msgs
  attr_accessor :ui_conn
  def initialize ( username, password ) 
      @off_ui_msgs = []
      @logger = Logger.new("fetion.log")
      @call_id = 0
      @response_handler_args = {}
      @response_handlers = {}
      @phone_num = username;  
      @password = password;  
      @domain = "fetion.com.cn";  
      @login_xml = '<args><device type="PC" version="0" client-version="3.2.0540" /><caps value="simple-im;im-session;temp-group;personal-group" /><events value="contact;permission;system-message;personal-group" /><user-info attributes="all" /><presence><basic value="400" desc="" /></presence></args>';          
      self.init  
  end
  
  def init  
      doc = REXML::Document.new(self.get_system_config())  
      sipc_proxy = ""  
      doc.elements.each("//sipc-proxy") do |element|  # using regexp should be faster  
          sipc_proxy = element.text  
      end
      server_uri = sipc_proxy.split(":")
      @server_ip = server_uri[0]
      @server_port = server_uri[1].to_i    
      #@SIPC = SIPC.new(sipc_proxy);  
      #@SIPClient = SIPClient.new(@SIPC.socket)
        
      sipc_url = ""   
      doc.elements.each("//ssi-app-sign-in") do |element|    
          sipc_url = element.text  
      end    
      @fetion_num = self.get_fetion_num(self.SSIAppSignIn(sipc_url));  
        
      doc.elements.each("//http-tunnel") do |element|    
          @http_tunnel = element.text  
      end    
  end
  
  def SSIAppSignIn(url)     
      uri = URI.parse(url);  
      path = uri.path + "?mobileno=" + @phone_num + "&pwd=" + @password  
      http = Net::HTTP.new(uri.host,uri.port)  
      http.use_ssl = true  
      http.verify_mode = OpenSSL::SSL::VERIFY_NONE # turn off SSL warning  
      resp, xml = http.get(path)  
        
      @cookie = resp.response['set-cookie'].split(';')[0]  
      #puts "cookie---------- #{@cookie}"  
      @ssic = @cookie.split("=")[1]  
      return xml  
  end
  
  def get_fetion_num(xml)  
      @uri = ""  
      doc = REXML::Document.new(xml)  
      doc.elements.each("//results/user") do |element|  
        @uri = element.attribute("uri").value  
      end   
      return @uri.scan(/sip:([0-9]+)@/)[0][0]  
  end
  
  def get_system_config()  
      uri = URI.parse("http://nav.fetion.com.cn/nav/getsystemconfig.aspx")  
      http = Net::HTTP.new(uri.host, uri.port)  
      params = sprintf('<config><user mobile-no="%s" /><client type="PC" version="3.2.0540" platform="W5.1" /><servers version="0" /><service-no version="0" /><parameters version="0" /><hints version="0" /><http-applications version="0" /><client-config version="0" /></config>',@phone_num)  
      headers = {  
        'Content-Type' => 'application/x-www-form-urlencoded'  
      }  
      resp = http.post(uri.path, params, headers)  
      return resp.body  
  end
  
  def get_response()  
      @cnonce = Digest::MD5.hexdigest(rand.to_s)  
      #puts "conce: #{@cnonce} , fetion_num: #{@fetion_num} , domain : #{@domain} ,password:#{@password}"  
      key = Digest::MD5.digest(@fetion_num + ":" + @domain + ":" + @password)  
      h1 = Digest::MD5.hexdigest(key + ":" + @nonce + ":" + @cnonce).upcase   
      h2 = Digest::MD5.hexdigest("REGISTER:" + @fetion_num).upcase  
      return Digest::MD5.hexdigest(h1+":" + @nonce + ":" + h2).upcase  
  end
  
  def send request_package, *args, &handler
    
    #p handler    
    @call_id += 1  
    @response_handlers[@call_id.to_s] = handler
    @response_handler_args[handler] = args
    request_package.head["I"] = @call_id.to_s
    request_package.head["Q"] = "#{@call_id} #{request_package.event}"
    request_package.head["F"] = @fetion_num
    if request_package.body
      request_package.head["L"] = request_package.body.size.to_s
    end
    request_package.domain = "fetion.com.cn"
    #p @response_handlers
    #p @call_id
    #puts request_package.to_log
    @logger.info("send"){request_package.to_log}
    @fetion_conn.send_data request_package.to_package
  end
  
  def login

    EventMachine::run do
      
      EventMachine::connect @server_ip, @server_port, FetionCallBack, self do |conn|
        @fetion_conn = conn
        self.login_part1
      end
      
    end
  end
  
  
  def login_part1
    puts "Login..."
    request1 = sprintf("R %s SIP-C/2.0\r\nF: %s\r\nI: 1\r\nQ: 1 R\r\nL: %s\r\n\r\n",@domain, @fetion_num, @login_xml.length);  
    request1 = request1 + @login_xml;
    
    send SIPCPackage.new(request1) do |response|
      @nonce = response.head["W"].scan(/nonce="(.*)"/)[0][0]
      request2 = sprintf("R %s SIP-C/2.0\r\nF: %s\r\nI: 2\r\nQ: 2 R\r\nA: Digest response=\"%s\",cnonce=\"%s\"\r\nL: %s\r\n\r\n", @domain, @fetion_num, get_response(), @cnonce, @login_xml.length);  
      request2 = request2 + @login_xml;
      send SIPCPackage.new(request2) do |response2|
        if response2.code == "200"
          puts "Login OK. Getting contact list..."
          get_contact_list
        end
      end
    end    
  end
  
  
  def get_contact_list
    package = SIPCPackage.new
    package.event = "S"
    #package.domain = "fetion.com.cn"
    #package.head["F"] = @fetion_num
    #package.head["I"] = "1"
    #package.head["Q"] = "1 S"
    package.head["N"] = "GetContactList"
    package.body = "<args><contacts><buddy-lists /><buddies attributes=\"all\" /><mobile-buddies attributes=\"all\" /><chat-friends /><blacklist /></contacts></args>"
    #package.head["L"] = package.body.size
    #puts package.to_package
    send package do |response|
      #puts "FROM SEVER"
      create_contact_list response
      update_contact_list      
    end
      
    #@fetion_conn.send_data package.to_package
  end
  
  def create_contact_list package
    @contact_list = []
    xml = REXML::Document.new(package.body)
    xml.elements.each("//buddy") do |buddy|
      contact = Contact.new buddy
      contact.type = :pc
      @contact_list << contact
    end
    xml.elements.each("//mobile-buddy") do |mobile_buddy|
      contact = Contact.new mobile_buddy
      contact.type = :mobile
      @contact_list << contact
    end
  end
  
  def update_contact_list
    @contact_list.each do |contact|
      update_contact contact
    end
    keep_alive
    set_ui
  end
  
  def set_ui
    EventMachine::run do 
      sock_addr = Socket.unpack_sockaddr_in EventMachine::get_sockname EventMachine::start_server "127.0.0.1", 0, FetionUI, self
      @local_port = sock_addr[0]
      puts "\"telnet 127.0.0.1 #{@local_port}\" to get manual."
    end
  end
  
  def update_contact contact
    package = SIPCPackage.new
    package.event = "S"
    package.head["N"] = "GetContactsInfo"
    package.body = "<args><contacts attributes=\"all\"><contact uri=\"#{contact.uri}\" /></contacts></args>"
    send package, contact do |response, args|
      if response.code = "200"
        args[0].update(response.body)
      else
        update_contact args[0]
      end
    end
  end
  
  def keep_alive_send
    package = SIPCPackage.new
    package.event = "R"
    send package do |response|
      #puts response.to_log
    end
    package = SIPCPackage.new
    package.event = "S"
    package.head["N"] = "GetContactPermission"
    package.body = "<args><permissions all=\"1\" objects=\"all\" /></args>"
    send package do |response|
      #puts response.to_log
    end
  end
  
  def keep_alive
    EventMachine::run do 
      timer = EventMachine::PeriodicTimer.new(300) do
        keep_alive_send
      end
    end
  end
  
  def send_msg to, msg, &handler
    package = SIPCPackage.new
    package.event = "M"
    package.head["C"] = "text/plain"
    package.head["K"] = "SaveHistory"
    package.body = msg
    if to =~ /1\d\d\d\d\d\d\d\d\d\d/
      if get_contact_by_mobile_no(to)
        package.head["T"] = get_contact_by_mobile_no(to).uri
      end
    else
      if get_contact_by_fetion_num(to)
        package.head["T"] = get_contact_by_fetion_num(to).uri
      end
    end
    if package.head["T"]
      send package do |response|
        handler.call response
      end
    else
      @ui_conn.error "@#{to} not found."
    end
  end
  
  def get_contact_by_mobile_no m_no
    re = nil
    @contact_list.each do |contact|
      re = contact if contact.mobile_no ==m_no
    end
    return re
  end
  
  def get_contact_by_fetion_num f_num
    re = nil
    @contact_list.each do |contact|
      re = contact if contact.fetion_num == f_num
    end
    re
  end
  
  def on_receive_msg package
    from = get_contact_by_uri(package.head["F"])
    msg = package.body
    if @ui_conn
      @ui_conn.on_receive_msg from, msg
    else
      @off_ui_msgs << package
    end
  end
  
  def check_off_ui_message
    @off_ui_msgs.each do |p|
      on_receive_msg p
    end
  end
  
  def get_contact_by_uri uri
    re = nil
    @contact_list.each do |contact|
      re = contact if contact.uri == uri
    end
    re
  end
  
  def list_contact
    cl = []
    @contact_list.each do |contact|
      cl << contact.to_dis
    end
    cl
  end
  
end

module FetionUI
  
  def initialize *args
    @fetion = args[0]
	  @fetion.ui_conn = self
  end
  
  
	def post_init
	  @welcom_str = <<EOS
        >>Welcome to Fetion<<
        
Command Manual:
  
  @[Phone Number]|[Fetion Numner] [Message]  -Send message to some contact.
    e.g. @12345678 test message blahhhh...^_^...
    
  ls  -List all the contact.
  
  bye -Disconnect this telnet console.
  
  help -Display this information.
  
Enjoy!  
EOS
	  send_to_user @welcom_str
	  @fetion.check_off_ui_message
	  
  end

  def receive_data(data)
    case data
    when /@\d+ .+/
      to = data.split(" ")[0][1..-1]
      msg = data[to.size+2..-1]
      @fetion.send_msg to, msg do |response|
        if response.code = "200"
          send_to_user "Sent Message OK"
        else
          send_to_user "Sending Error.\nCode = #{response.code} #{response.code_event}"
        end
      end
    when /ls\s+/
      list_contact
    when /help\s+/
      send_to_user @welcom_str
    when /bye\s+/
      send_to_user "Bye"
      close_connection
    else
      send_to_user "Bad Command: #{data}"
    end
  end
  
  def on_receive_msg from, msg
    str = "\r\n#{from.to_dis} says: #{msg}\r\n"
    send_to_user str
  end
  
  def list_contact
    cl = @fetion.list_contact
    str = "\r\n"
    cl.each do |c|
      str += (c+"\r\n")
    end
    send_to_user str
  end

  def send_to_user str
    send_data "\r\n#{str}\r\n"
    promt
  end


  def promt
    send_data "\r\n>Fetion : "
  end
  
  def unbind
    @fetion.ui_conn = nil
  end
  
  def error str
    send_to_user "ERROR: #{str}"
  end

end  

module FetionCallBack
  
  def initialize *args
    super
    @fetion = args[0]
  end
  
  def post_init
   # @fetion.login_part1
  end
  
  
  def receive_data data
    #p data+"=========="
    
    SIPCPackage.new.parse(data) do |package|
      
      #puts package.to_log
      @fetion.logger.info ("receive"){package.to_log}
      case package.event
      when "response"
        #p @fetion.response_handlers
        #p package.head["I"]
        @fetion.response_handlers[package.head["I"]].call package, @fetion.response_handler_args[@fetion.response_handlers[package.head["I"]]]
      when "M"
        @fetion.on_receive_msg package
      when "NB"
      end
      
    end
    
    #package = ::SIPCPackage.new(data)

  end
  
  def unbind
    puts "connection broke."
    EventMachine::stop_event_loop
  end
  
end



fetion = Fetion.new ARGV[0],ARGV[1] 
fetion.login 
      